<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Exception;
use Srv\Apps\Proto\Enum\ErrCodeEnum;
use Srv\Apps\Proto\Enum\LangKeyEnum;
use Srv\Libs\Boot;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommTime;
use Srv\Libs\ConfModel\ProjectConf;
use Srv\Libs\ConfModel\RouteConf;
use Srv\Libs\ConfModel\RouteConfData;
use Srv\Libs\ConfModel\WebSocketConf;
use Srv\Libs\Tools\Des;
use Srv\Libs\Tools\Http;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Server\Task;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use Throwable;

final class ResponseMain
{
    public const AUTH_DENY                          = 'DENY';                       // DENY
    public const AUTH_NULL                          = 'NULL';                       // NULL
    public const AUTH_LOGIN                         = 'LOGIN';                      // LOGIN
    public const DEBUG_TIME                         = '_Time_';                     // debug-time
    public const DEBUG_NAME                         = '_Name_';                     // debug-name
    public const DEBUG_LOGS                         = '_Logs_';                     // debug-logs
    public const DEBUG_PRINT                        = '_Print_';                    // debug-print
    public const KEY_RAW                            = '_Raw_';                      // Raw
    public const KEY_ERROR                          = 'Error';                      // Error
    public const KEY_EXCEPTION                      = 'Exception';                  // Exception
    public const KEY_AUTH                           = 'Auth';                       // Auth
    public const KEY_NOT_FOUND_REQUEST              = 'NotFoundRequest';            // NotFoundRequest
    public const SERVER_HOSTNAME                    = 'HOSTNAME';                   // 服务名
    public const SERVER_DOCUMENT_ROOT               = 'DOCUMENT_ROOT';              // 根目录
    public const SERVER_REQUEST_TIME_START          = 'REQUEST_TIME_START';         // 请求开始时间[毫秒]
    public const SERVER_SERVER_NAME                 = 'SERVER_NAME';                // 主机名
    public const SERVER_SERVER_PORT                 = 'SERVER_PORT';                // 服务端口
    public const SERVER_REMOTE_PORT                 = 'REMOTE_PORT';                // 客户端口
    public const SERVER_REMOTE_ADDR                 = 'REMOTE_ADDR';                // 客户地址
    public const SERVER_SERVER_PROTOCOL             = 'SERVER_PROTOCOL';            // 服务协议
    public const SERVER_REQUEST_URI                 = 'REQUEST_URI';                // 请求URI
    public const SERVER_REQUEST_METHOD              = 'REQUEST_METHOD';             // 请求方法
    public const SERVER_QUERY_STRING                = 'QUERY_STRING';               // 请求query
    public const HEADER_X_DEV_ID                    = 'X-DEV-ID';                   // 设备ID `/^[A-Z\d]{8}-[A-Z\d]{4}-[A-Z\d]{4}-[A-Z\d]{4}-[A-Z\d]{12}$/`
    public const HEADER_X_DEV_NAME                  = 'X-DEV-NAME';                 // 设备名 `/^[.*]{3,64}$/`
    public const HEADER_X_DEV_SYSTEM                = 'X-DEV-SYSTEM';               // 设备系统和版本 `/^[.*]{3,64}$/`
    public const HEADER_X_DEV_TIME                  = 'X-DEV-TIME';                 // 设备当前时间`[毫秒]` `/^\d{13}$/`
    public const HEADER_X_DEV_VERSION               = 'X-DEV-VERSION';              // 设备版本字符串 `/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,9}$/`
    public const HEADER_X_DEV_LANG                  = 'X-DEV-LANG';                 // 设备语言 `/^[a-zA-Z_-]{2,8}$/`
    public const HEADER_X_DEV_SCREEN                = 'X-DEV-SCREEN';               // 设备屏幕宽x高 `/^(\d{3,6})x(\d{3,6})$/`
    public const HEADER_X_DEV_NET                   = 'X-DEV-NET';                  // 设备网络 `/^[a-zA-Z\d]{2,8}$/`
    public const HEADER_X_DEV_LOCATION              = 'X-DEV-LOCATION';             // 设备位置经纬度 `/^((-)?\d{1,3}\.\d{1,12}),((-)?\d{1,2}\.\d{1,12})$/`
    public const HEADER_X_DEV_OFFSET                = 'X-DEV-OFFSET';               // 设备时区偏移值`[秒]` `[相对UTC的正负偏移秒数]` `[可空]` `/^(-)?\d{1,5}$/`
    public const HEADER_X_APP_KEY                   = 'X-APP-KEY';                  // 应用Key `/^[a-zA-Z\d]{16}$/`
    public const HEADER_X_APP_PLATFORM              = 'X-APP-PLATFORM';             // 应用平台类型 `/^\w{2,16}$/`
    public const HEADER_X_APP_CHANNEL               = 'X-APP-CHANNEL';              // 应用渠道名和BundleId `/^([\da-zA-Z\-]{3,16})\/([\da-zA-Z\-.]{5,32})$/`
    public const HEADER_X_APP_NONCE                 = 'X-APP-NONCE';                // 应用随机数 `/^\d{16}$/`
    public const HEADER_X_APP_TOKEN                 = 'X-APP-TOKEN';                // 应用令牌 /^[a-zA-Z\d+\/=&#124;]{32,2048}$/
    public const HEADER_X_SIG                       = 'X-SIG';                      // sha1数据签名 `/^[a-fA-F\d]{40}$/`
    public const HEADER_X_KEY                       = 'X-KEY';                      // 秘钥key `/^[.*]{4,}$/`
    public const HEADER_X_SIG_SKIP                  = 'X-SIG-SKIP';                 // 跳过签名的密钥 `/^[.*]{16,}$/`
    public const HEADER_CONTENT_TYPE                = 'CONTENT-TYPE';               // 传输内容类型 `[application/json]` `[application/octet-binary]` `[application/xml]` `[application/protobuf]` `[text/html]`
    public const HEADER_X_CLIENT_SCHEME             = 'X-CLIENT-SCHEME';            // X-Client-Scheme
    public const HEADER_X_CLIENT_HOST               = 'X-CLIENT-HOST';              // X-Client-Host
    public const HEADER_X_CLIENT_PORT               = 'X-CLIENT-PORT';              // X-Client-Port
    public const HEADER_X_CLIENT_IP                 = 'X-CLIENT-IP';                // X-Client-Ip
    public const HEADER_X_FORWARDED_FOR             = 'X-FORWARDED-FOR';            // X-Forwarded-For
    public const HEADER_USER_AGENT                  = 'USER-AGENT';                 // User-Agent
    public const HEADER_ACCEPT_LANGUAGE             = 'ACCEPT-LANGUAGE';            // Accept-Language
    public const HEADER_REFERER                     = 'REFERER';                    // Referer
    public const HEADER_COOKIE                      = 'COOKIE';                     // Cookie
    public const HEADER_ACCEPT                      = 'ACCEPT';                     // Accept
    private static array $ackDataList               = [];                           // ack数据[webSocket]
    private static array $runAuthFailedList         = [];                           // 认证失败
    private static array $runNotFoundList           = [];                           // 请求未找到
    private static array $runExceptionList          = [];                           // 异常
    private static array $runErrorList              = [];                           // 错误
    private static array $runLogList                = [];                           // 运行日志
    private static int $webSocketFd                 = 0;                            // webSocketFd
    private static bool $isWebSocket                = false;                        // 是否是webSocket模式
    private static ?Server $WebSocketServer         = null;                         // 服务对象
    private static string $serverName               = '';                           // 服务器名
    private static int $startTimeMicro              = 0;                            // 开始时间
    private static array $allowMethodList           = [];                           // 允许请求的方法名
    private static array $accessHeaderList          = [                             // 允许跨域Head头Key列表
        self::HEADER_CONTENT_TYPE,
        self::HEADER_X_DEV_ID,
        self::HEADER_X_DEV_NAME,
        self::HEADER_X_DEV_SYSTEM,
        self::HEADER_X_DEV_TIME,
        self::HEADER_X_DEV_VERSION,
        self::HEADER_X_DEV_LANG,
        self::HEADER_X_DEV_SCREEN,
        self::HEADER_X_DEV_NET,
        self::HEADER_X_DEV_LOCATION,
        self::HEADER_X_DEV_OFFSET,
        self::HEADER_X_APP_KEY,
        self::HEADER_X_APP_PLATFORM,
        self::HEADER_X_APP_CHANNEL,
        self::HEADER_X_APP_NONCE,
        self::HEADER_X_APP_TOKEN,
        self::HEADER_X_SIG,
        self::HEADER_X_KEY,
        self::HEADER_X_SIG_SKIP,
    ];
    private static array $headAllowKeyList          = [                             // Head头Key列表[全大写]
        self::HEADER_X_DEV_ID,
        self::HEADER_X_DEV_NAME,
        self::HEADER_X_DEV_SYSTEM,
        self::HEADER_X_DEV_TIME,
        self::HEADER_X_DEV_VERSION,
        self::HEADER_X_DEV_LANG,
        self::HEADER_X_DEV_SCREEN,
        self::HEADER_X_DEV_NET,
        self::HEADER_X_DEV_LOCATION,
        self::HEADER_X_DEV_OFFSET,
        self::HEADER_X_APP_KEY,
        self::HEADER_X_APP_PLATFORM,
        self::HEADER_X_APP_CHANNEL,
        self::HEADER_X_APP_NONCE,
        self::HEADER_X_APP_TOKEN,
        self::HEADER_X_SIG,
        self::HEADER_X_KEY,
        self::HEADER_X_SIG_SKIP,
        self::HEADER_CONTENT_TYPE,
        self::HEADER_X_CLIENT_SCHEME,
        self::HEADER_X_CLIENT_HOST,
        self::HEADER_X_CLIENT_PORT,
        self::HEADER_X_CLIENT_IP,
        self::HEADER_X_FORWARDED_FOR,
        self::HEADER_USER_AGENT,
        self::HEADER_ACCEPT_LANGUAGE,
        self::HEADER_REFERER,
        self::HEADER_COOKIE,
        self::HEADER_ACCEPT,
    ];

    /**
     * @param WebSocketConf $WebSocketConf
     * @param ProjectConf $ProjectConf
     * @return void
     * init
     */
    public static function init(WebSocketConf $WebSocketConf, ProjectConf $ProjectConf):void
    {
        self::$serverName           = $ProjectConf->getServerName();
        self::$WebSocketServer      = null;
        self::$webSocketFd          = 0;
        set_exception_handler([__CLASS__,   'exceptionHandler']);
        set_error_handler([__CLASS__,       'errorHandler'], E_ALL);
        WebSocketService::init($WebSocketConf, $ProjectConf);
    }

    /**
     * @param int $errNo
     * @param string $errStr
     * @param string $errFile
     * @param int $errLine
     * @return void
     * errorHandler
     */
    public static function errorHandler(int $errNo, string $errStr, string $errFile, int $errLine):void
    {
        Logs::error('[Error]'.CommJson::encodeArray(['errNo' => $errNo, 'errStr' => $errStr]), $errLine, $errFile);
        self::$runErrorList[]       = ['file' => $errFile, 'line' => $errLine, 'msg' => $errStr, 'errNo' => $errNo];
        ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SERVER_ERROR, Lang::get(LangKeyEnum::LANG_SYS_SERVER_ERROR));
    }

    /**
     * @param Throwable $exception
     * @return void
     * exceptionHandler
     */
    public static function exceptionHandler(Throwable $exception):void
    {
        Logs::exception('['.CommString::getClassOnlyName(get_class($exception)).']'.$exception->getMessage(), $exception->getLine(), $exception->getFile());
        self::$runExceptionList[]   = ['file' => $exception->getFile(), 'line' => $exception->getLine(), 'msg' => $exception->getMessage(), 'trace' => $exception->getTraceAsString()];
        ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SERVER_EXCEPTION, Lang::get(LangKeyEnum::LANG_SYS_SERVER_EXCEPTION));
    }

    /**
     * @param Throwable $exception
     * @return void
     * runExceptionHandler
     */
    public static function runExceptionHandler(Throwable $exception):void
    {
        Logs::exception('['.CommString::getClassOnlyName(get_class($exception)).']'.$exception->getMessage(), $exception->getLine(), $exception->getFile());
        self::$runExceptionList[]   = ['file' => $exception->getFile(), 'line' => $exception->getLine(), 'msg' => $exception->getMessage(), 'trace' => $exception->getTraceAsString()];
        ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SERVER_EXCEPTION, Lang::get(LangKeyEnum::LANG_SYS_SERVER_EXCEPTION));
    }

    /**
     * @param string $urlPath
     * @param string $groupName
     * @return void
     * runNotFoundRequest
     */
    private static function runNotFoundRequest(string $urlPath, string $groupName):void
    {
        self::$runNotFoundList[]    = ['urlPath' => $urlPath, 'groupName' => $groupName];
        ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_REQUEST_NOT_FOUND, Lang::get(LangKeyEnum::LANG_SYS_REQUEST_NOT_FOUND));
    }

    /**
     * @param string $urlPath
     * @param string $groupName
     * @param string $authStr
     * @return void
     * runNotPermission
     */
    private static function runNotPermission(string $urlPath, string $groupName, string $authStr):void
    {
        self::$runAuthFailedList[]  = ['urlPath' => $urlPath, 'groupName' => $groupName, 'authStr' => $authStr];
        // AuthData::isLogin() 判断用户是否登入
        if($authStr === self::AUTH_LOGIN || !AuthData::isLogin()) {
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_AUTH_NOT_LOGIN, Lang::get(LangKeyEnum::LANG_SYS_AUTH_NOT_LOGIN));
        }else{
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_AUTH_PERMISSION_DENIED, Lang::get(LangKeyEnum::LANG_SYS_AUTH_PERMISSION_DENIED));
        }
    }

    /**
     * @param string $authKey
     * @return bool
     * auth
     */
    private static function auth(string $authKey):bool
    {
        switch($authKey){
            case self::AUTH_DENY: {
                return false;
            }
            case '':
            case self::AUTH_NULL: {
                return true;
            }
            case self::AUTH_LOGIN: {
                return AuthData::isLogin();
            }
            default:{
                return AuthData::checkAuth($authKey);
            }
        }
    }

    /**
     * @return void
     * start
     */
    private static function start():void
    {
        ob_start();
        self::$startTimeMicro   = CommTime::getTimeMicro();
    }

    /**
     * @param string $urlPath
     * @param string $groupName
     * @param bool $isWebSocket
     * @param string $reqContent
     * @param string $reqContentSrc
     * @param $params
     * @param bool $isCheckType
     * @return bool
     * run
     */
    private static function run(string $urlPath, string $groupName, bool $isWebSocket, string $reqContent, string $reqContentSrc, $params, bool $isCheckType = true):bool
    {
        $RouteConfData          = $isWebSocket ? Route::getSocksPathConf($groupName, $urlPath) : Route::getWebsPathConf($groupName, $urlPath);
        if(!($RouteConfData instanceof RouteConfData)){
            $RouteConf              = Conf::getRouteConfGroup()->getByGroupName($groupName);
            if($RouteConf instanceof RouteConf){
                $urlPathSet         = trim($RouteConf->getActionNotFoundRequest());
                if(strlen($urlPathSet) > 0){
                    $RouteConfData  = $isWebSocket ? Route::getSocksPathConf($groupName, $urlPathSet) : Route::getWebsPathConf($groupName, $urlPathSet);
                    if($RouteConfData instanceof RouteConfData){
                        $RouteConfData->initReqContent($reqContent, $reqContentSrc, $urlPath);
                        return $RouteConfData->runAction($params, $isCheckType);
                    }
                }
            }
            self::runNotFoundRequest($urlPath, $groupName);
            return false;
        }
        if(!self::auth($RouteConfData->getAuthStr())){
            $RouteConf              = Conf::getRouteConfGroup()->getByGroupName($groupName);
            if($RouteConf instanceof RouteConf){
                $urlPathSet         = trim($RouteConf->getActionNotPermission());
                if(strlen($urlPathSet) > 0){
                    $RouteConfData  = $isWebSocket ? Route::getSocksPathConf($groupName, $urlPathSet) : Route::getWebsPathConf($groupName, $urlPathSet);
                    if($RouteConfData instanceof RouteConfData){
                        $RouteConfData->initReqContent($reqContent, $reqContentSrc, $urlPath);
                        return $RouteConfData->runAction($params, $isCheckType);
                    }
                }
            }
            self::runNotPermission($urlPath, $groupName, $RouteConfData->getAuthStr());
            return false;
        }
        $RouteConfData->initReqContent($reqContent, $reqContentSrc);
        return $RouteConfData->runAction($params, $isCheckType);
    }

    /**
     * @return void
     * stop
     */
    public static function stop():void
    {
        $obStr                  = ob_get_clean();
        if(!is_string($obStr)) $obStr = '';
        $debugData              = [
            self::DEBUG_TIME    => self::getRunTime(),
            self::DEBUG_NAME    => self::$serverName,
            self::DEBUG_LOGS    => self::$runLogList,
            self::DEBUG_PRINT   => $obStr,
        ];
        if(strlen($obStr) > 0) Logs::debug($obStr, __LINE__, __FILE__);
        if(count(self::$runLogList) > 0) Logs::debug(CommJson::encodeArray(self::$runLogList), __LINE__, __FILE__);
        if(count(self::$runExceptionList) > 0) $debugData[self::KEY_EXCEPTION] = self::$runExceptionList;
        if(count(self::$runErrorList) > 0) $debugData[self::KEY_ERROR] = self::$runErrorList;
        if(count(self::$runNotFoundList) > 0){
            $debugData[self::KEY_NOT_FOUND_REQUEST] = self::$runNotFoundList;
            Logs::warn(CommJson::encodeArray(self::$runNotFoundList), __LINE__, __FILE__);
        }
        if(count(self::$runAuthFailedList) > 0){
            $debugData[self::KEY_AUTH]              = self::$runAuthFailedList;
            Logs::info(CommJson::encodeArray(self::$runAuthFailedList), __LINE__, __FILE__);
        }
        ResponseData::setDebugData($debugData);
    }

    /**
     * @param int $errCode
     * @param string $errMsg
     * @param int $allowDiffTime
     * @param RouteConf $RouteConf
     * @return bool
     * checkHeaderData
     */
    private static function checkHeaderData(int &$errCode, string &$errMsg, int $allowDiffTime, RouteConf $RouteConf):bool
    {
        foreach (self::$accessHeaderList as $headerKey){
            $headerVal                  = EnvData::getByHeaderRouteItem($headerKey, $RouteConf);
            switch($headerKey){
                case self::HEADER_X_APP_NONCE: {        // 应用随机数
                    if(!is_string($headerVal) || !preg_match('/^\d{16}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_NONCE;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_NONCE);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_LANG: {         // 设备语言
                    if(!is_string($headerVal) || !preg_match('/^[a-zA-Z_-]{2,8}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_LANG;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_LANG);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_ID: {           // 设备ID
                    if(!is_string($headerVal) || !preg_match('/^[A-Z\d]{8}-[A-Z\d]{4}-[A-Z\d]{4}-[A-Z\d]{4}-[A-Z\d]{12}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_ID;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_ID);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_NAME: {         // 设备名
                    if(!is_string($headerVal) || strlen($headerVal) < 3 || strlen($headerVal) > 64){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_NAME;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_NAME);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_SYSTEM: {       // 设备系统和版本
                    if(!is_string($headerVal) || strlen($headerVal) < 3 || strlen($headerVal) > 64){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_SYSTEM;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_SYSTEM);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_APP_KEY: {          // 应用Key
                    if(!is_string($headerVal) || !preg_match('/^[a-zA-Z\d]{16}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_KEY;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_KEY);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_TIME: {         // 设备当前时间[毫秒]
                    if(!is_string($headerVal) || !preg_match('/^\d{13}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_TIME;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_TIME);
                        return false;
                    }
                    if($allowDiffTime > 0 && abs(CommTime::getTimeMilli() - intval($headerVal)) > ($allowDiffTime*1000)){ // 检查时差是否允许
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_TIME;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_TIME);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_VERSION: {      // 设备版本字符串
                    if(!is_string($headerVal) || !preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,9}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_VERSION;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_VERSION);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_SCREEN: {       // 设备屏幕宽x高
                    if(!is_string($headerVal) || !preg_match('/^(\d{3,6})x(\d{3,6})$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_SCREEN;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_SCREEN);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_NET: {          // 设备网络
                    if(!is_string($headerVal) || !preg_match('/^[a-zA-Z\d]{2,8}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_NET;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_NET);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_LOCATION: {     // 设备位置经度,纬度[可空内容]
                    if(!is_string($headerVal) || (strlen($headerVal) > 0 && !preg_match('/^((-)?\d{1,3}\.\d{1,12}),((-)?\d{1,2}\.\d{1,12})$/', $headerVal))){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_LOCATION;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_LOCATION);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_DEV_OFFSET: {       // 设备时区偏移值[秒][相对UTC的正负偏移秒数][可空内容]
                    if(!is_string($headerVal) || (strlen($headerVal) > 0 && !preg_match('/^(-)?\d{1,5}$/', $headerVal))){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_DEV_OFFSET;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_DEV_OFFSET);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_APP_PLATFORM: {     // 应用平台类型
                    if(!is_string($headerVal) || !preg_match('/^\w{2,16}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_PLATFORM;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_PLATFORM);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_APP_CHANNEL: {      // 应用渠道名和BundleId
                    if(!is_string($headerVal) || !preg_match('/^([\da-zA-Z\-_]{3,16})\/([\da-zA-Z\-.]{5,32})$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_CHANNEL;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_CHANNEL);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_APP_TOKEN: {        // 应用令牌[可空内容]
                    if(!is_string($headerVal) || (strlen($headerVal) > 0 && !preg_match('/^[a-zA-Z\d+\/=|]{32,2048}$/', $headerVal))){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_TOKE;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_TOKEN);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_SIG: {              // sha1数据签名
                    if(!is_string($headerVal) || !preg_match('/^[a-fA-F\d]{40}$/', $headerVal)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_SIG;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_SIG);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_KEY: {              // 加密KEY[可选]
                    if(!is_null($headerVal) && (!is_string($headerVal) || strlen($headerVal) < 4)){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_KEY;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_KEY);
                        return false;
                    }
                    break;
                }
                case self::HEADER_X_SIG_SKIP: {         // 跳过签名的密钥[可选][可空]
                    if(!is_null($headerVal) && (!is_string($headerVal) || (strlen($headerVal) > 0 && strlen($headerVal) < 16))){
                        $errCode        = ErrCodeEnum::ERR_SYS_HEADER_APP_KEY;
                        $errMsg         = Lang::get(LangKeyEnum::LANG_SYS_HEADER_APP_KEY);
                        return false;
                    }
                    break;
                }
            }
        }
        return true;
    }

    /**
     * @param Request $request
     * @param int $fd
     * @return void
     * initResponse
     */
    private static function initResponse(Request $request, int $fd):void
    {
        self::$runAuthFailedList    = [];
        self::$runNotFoundList      = [];
        self::$runExceptionList     = [];
        self::$runErrorList         = [];
        self::$runLogList           = [];
        self::$webSocketFd          = $fd;
        self::$isWebSocket          = false;
        $serverData                 = [
            self::SERVER_HOSTNAME           => self::$serverName,
            self::SERVER_DOCUMENT_ROOT      => Boot::getRootPath(),
            self::SERVER_REQUEST_TIME_START => CommTime::getTimeMicro(),
            self::SERVER_SERVER_NAME        => $request->header['host']??'',
            self::SERVER_SERVER_PORT        => $request->header['server_port']??($request->server['server_port']??''),
            self::SERVER_REMOTE_PORT        => $request->header['remote_port']??($request->server['remote_port']??''),
            self::SERVER_REMOTE_ADDR        => $request->header['remote_addr']??($request->server['remote_addr']??''),
            self::SERVER_SERVER_PROTOCOL    => $request->header['scheme']??'',
            self::SERVER_REQUEST_URI        => $request->server['request_uri']??'',
            self::SERVER_REQUEST_METHOD     => $request->server['request_method']??'',
            self::SERVER_QUERY_STRING       => $request->server['query_string']??'',
        ];
        $headerKeyAliasList         = [
            'X-FORWARDED-PROTO'     => self::HEADER_X_CLIENT_SCHEME,
        ];
        $headerData                 = [];
        if(count($request->header) > 0) foreach($request->header as $key => $val){
            $key                    = strtoupper($key);
            $key                    = $headerKeyAliasList[$key]??$key;
            if(!in_array($key, self::$headAllowKeyList, true)) continue;
            $headerData[Http::getHeaderName($key)] = $val;
        }
        EnvData::init($request->get??[], $request->post??[], $request->cookie??[], [], [], $request->files??[], $serverData, $headerData, $request->getContent(), 1);
        ResponseData::init();
    }

    /**
     * @param int $fd
     * @param string $groupName
     * @return bool
     * initMessage
     */
    private static function initMessage(int $fd, string &$groupName):bool
    {
        self::$runAuthFailedList    = [];
        self::$runNotFoundList      = [];
        self::$runExceptionList     = [];
        self::$runErrorList         = [];
        self::$runLogList           = [];
        self::$webSocketFd          = $fd;
        self::$isWebSocket          = true;
        $time                       = 0;
        $userId                     = '';
        $envStr                     = '';
        $groupName                  = '';
        $loginKey                   = '';
        $loginExpTime               = 0;
        $loginPlatform              = '';
        $loginDevId                 = '';
        $loginAuth                  = '';
        $langType                   = '';
        if(!WebSocketTable::getWsConnTableData($fd, $time, $userId, $envStr, $groupName, $loginKey, $loginExpTime, $loginPlatform, $loginDevId, $loginAuth, $langType)) return false;
        if(!EnvData::initFrom($envStr)) return false;
        AuthData::restore(intval($userId), $loginKey, $loginExpTime, $loginPlatform, $loginDevId, $loginAuth);
        Lang::setLangType($langType, true);
        ResponseData::init();
        return true;
    }

    /**
     * @param Request $request
     * @param Response $response
     * @return void
     * serviceRequest
     */
    public static function serviceRequest(Request $request, Response $response):void
    {
        if(strlen(self::$serverName) > 0) $response->header('Server', self::$serverName);
        $response->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
        $response->header('Pragma', 'no-cache');
        $response->header('Expires', '-1');
        $requestMethod              = strtoupper($request->server['request_method']??'');
        if($requestMethod === 'OPTIONS'){                               // 允许跨域
            $optionsHeaderList      = [
                'Access-Control-Max-Age'            => '3600',
                'Access-Control-Allow-Origin'       => '*',
                'Access-Control-Allow-Methods'      => implode(',', self::$allowMethodList),
                'Access-Control-Allow-Headers'      => strtolower(implode(',', self::$accessHeaderList)),
                'Access-Control-Allow-Credentials'  => 'true',
                'Access-Control-Expose-Headers'     => strtolower(self::HEADER_CONTENT_TYPE.','.self::HEADER_X_SIG.','.self::HEADER_X_KEY),
            ];
            foreach ($optionsHeaderList as $optionsHeaderKey => $optionsHeaderVal) $response->header($optionsHeaderKey, $optionsHeaderVal);
            $response->status(204, 'No Content');
            $response->end('');
            return;
        }
        if(!in_array($requestMethod, self::$allowMethodList, true)){    // 不允许的请求方法
            $response->status(405, ' ');
            $response->end('');
            return;
        }
        self::initResponse($request, 0);
        $groupKey               = EnvData::getByHeaderOrderItem(self::HEADER_X_APP_KEY, [EnvData::KEY_HEADER, EnvData::KEY_COOKIE, EnvData::KEY_POST, EnvData::KEY_GET]);
        $RouteConf              = (is_string($groupKey) && strlen($groupKey) > 0) ? Conf::getRouteConfGroup()->getByGroupKey($groupKey) : Conf::getRouteConfGroup()->getByGroupName(Route::getGroupNameWebsDefault());
        if(!($RouteConf instanceof RouteConf)){                         // groupName错误
            $response->status(405, ' ');
            $response->end('');
            return;
        }
        // 解析令牌
        $appToken                   = EnvData::getByHeaderRouteItem(self::HEADER_X_APP_TOKEN, $RouteConf);
        if(is_string($appToken) && strlen($appToken) > 32){
            $devId                  = strval(EnvData::getByHeaderRouteItem(self::HEADER_X_DEV_ID, $RouteConf));
            $appPlatform            = strval(EnvData::getByHeaderRouteItem(self::HEADER_X_APP_PLATFORM, $RouteConf));
            AuthData::init($appToken, $devId, $appPlatform);
        }else{
            AuthData::reset();
        }
        // 解析语言
        $devLang                    = EnvData::getByHeaderRouteItem(self::HEADER_X_DEV_LANG, $RouteConf);
        if(!is_string($devLang)){
            $langData               = Http::getAcceptLanguageList();
            if(count($langData) > 0) $devLang = $langData[0][0]??'';
        }
        if(is_string($devLang) && strlen($devLang) > 1) Lang::setLangType($devLang, true);
        // 开始处理请求
        self::start();
        ResponseData::initRspContentHandleType($RouteConf->getRspContentType(), $RouteConf->getRspContentSecret(), $RouteConf->isRspContentSig(), $RouteConf->getAppSecret(), $RouteConf->getSmartyException(), $RouteConf->getSmartyError(), $RouteConf->getSmartyNotFound(), $RouteConf->getSmartyNoPermission(), $RouteConf->getSmartyParamFail());
        // 请求头检查
        RequestHeader::init($RouteConf);    // 初始化
        $errCode                = 0;
        $errMsg                 = '';
        if($RouteConf->isReqHeaderCheck() && !self::checkHeaderData($errCode, $errMsg, $RouteConf->getAllowDiffTime(), $RouteConf)){
            ResponseData::rspErrAuto($errCode, $errMsg);
            ResponseData::responseEnd($response, $RouteConf);
            return;
        }
        // 识别表单模式
        $isFromMode             = is_array($request->files);
        if(!$isFromMode){
            $contentType        = strtolower(strval(EnvData::getDataByHeaderItem(self::HEADER_CONTENT_TYPE)));
            if(strpos($contentType, 'form-data') !== false || strpos($contentType, 'form-urlencoded') !== false) $isFromMode = true;
        }
        $reqContentSrc          = $isFromMode ? strval(EnvData::getDataByPostItem(self::KEY_RAW)) : EnvData::getDataByRawContent();
        $reqContentSrcLength    = strlen($reqContentSrc);
        // 验证签名
        if($RouteConf->isReqContentSig() && (!$RouteConf->isSigSkip() || strlen($RouteConf->getSigSkipSecret()) < 1 || $RouteConf->getSigSkipSecret() !== EnvData::getByHeaderRouteItem(self::HEADER_X_SIG_SKIP, $RouteConf))){
            $sigCheckResult             = false;
            $headSrcSig                 = EnvData::getByHeaderRouteItem(self::HEADER_X_SIG, $RouteConf);
            if(is_string($headSrcSig)){
                $headSrcResult          = true;
                $headSrcData            = [];
                foreach (self::$accessHeaderList as $accessHeaderName){
                    if(in_array($accessHeaderName, [self::HEADER_X_SIG, self::HEADER_X_KEY, self::HEADER_X_SIG_SKIP, self::HEADER_X_DEV_OFFSET, self::HEADER_CONTENT_TYPE], true)) continue;
                    $headSrcVal         = EnvData::getByHeaderRouteItem($accessHeaderName, $RouteConf);
                    if($headSrcVal === null){
                        $headSrcResult  = false;
                        break;
                    }
                    $headSrcData[strtolower($accessHeaderName)] = $headSrcVal;
                }
                if($headSrcResult && count($headSrcData) > 0){
                    ksort($headSrcData);
                    $sigSrcStr                      = '';
                    foreach($headSrcData as $headSrcKey => $headSrcVal) $sigSrcStr .= (strlen($sigSrcStr) < 1 ? '' : '&').$headSrcKey.'='.$headSrcVal;
                    // 请求的加密状态
                    $reqContentSecret               = true;
                    switch (strtoupper($RouteConf->getReqContentSecret())){
                        case ResponseData::REQ_CONTENT_SECRET_NONE:{ $reqContentSecret = false; break; }
                        case ResponseData::REQ_CONTENT_SECRET_RAND_AUTO:{
                            $headerKey              = EnvData::getByHeaderRouteItem(ResponseMain::HEADER_X_KEY, $RouteConf);
                            $reqContentSecret       = !(!is_string($headerKey) || strlen($headerKey) < 1 || $headerKey === ResponseData::REQ_CONTENT_SECRET_NONCE);
                            break;
                        }
                    }
                    $sigSrcStr          .= $RouteConf->getAppSecret().($reqContentSrcLength < 1 ? '' : (!$reqContentSecret ? $reqContentSrc : sha1(base64_encode($reqContentSrc))));
                    if($headSrcSig === sha1($sigSrcStr)) $sigCheckResult = true;
                }
            }
            if(!$sigCheckResult){
                ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_HEADER_SIG, Lang::get(LangKeyEnum::LANG_SYS_HEADER_SIG));
                ResponseData::responseEnd($response, $RouteConf);
                return;
            }
        }
        // 解密内容
        $reqContent         = '';
        if(!ResponseData::decryptReqContent($RouteConf->getReqContentSecret(), $reqContentSrc, $reqContent, $RouteConf)){   // 解密失败
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SECRET_DECRYPT, Lang::get(LangKeyEnum::LANG_SYS_SECRET_DECRYPT));
            ResponseData::responseEnd($response, $RouteConf);
            return;
        }
        // 解码内容
        $params             = null;
        $isCheckType        = true;
        if(!ResponseData::decodeReqContentWebs($RouteConf->getReqContentType(), $reqContent, $params, $isCheckType)){       // 解码失败
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SECRET_DECODE, Lang::get(LangKeyEnum::LANG_SYS_SECRET_DECODE));
            ResponseData::responseEnd($response, $RouteConf);
            return;
        }
        $headSrcAppNonce    = EnvData::getByHeaderRouteItem(self::HEADER_X_APP_NONCE, $RouteConf);
        if(is_numeric($headSrcAppNonce)) ResponseData::setReqId(intval($headSrcAppNonce));
        try{
            $runResult      = self::run(Route::getUrlPath(), $RouteConf->getGroupName(), false, $reqContent, $reqContentSrc, $params, $isCheckType);
            unset($runResult);
        }catch(Exception $Exception){
            self::runExceptionHandler($Exception);
        }
        ResponseData::responseEnd($response, $RouteConf);
    }

    /**
     * @param Server $WebSocketServer
     * @param Request $request
     * @return void
     * serviceOpen
     */
    public static function serviceOpen(Server $WebSocketServer, Request $request):void
    {
        self::initResponse($request, $request->fd);
        $groupKey                   = EnvData::getByHeaderOrderItem(self::HEADER_X_APP_KEY, [EnvData::KEY_HEADER, EnvData::KEY_COOKIE, EnvData::KEY_POST, EnvData::KEY_GET]);
        $RouteConf                  = (is_string($groupKey) && strlen($groupKey) > 0) ? Conf::getRouteConfGroup()->getByGroupKey($groupKey) : Conf::getRouteConfGroup()->getByGroupName(Route::getGroupNameSocksDefault());
        if(!($RouteConf instanceof RouteConf)){
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_GROUP_NAME, Lang::get(LangKeyEnum::LANG_SYS_GROUP_NAME));
            ResponseData::pushEnd($WebSocketServer, $request->fd);
            $WebSocketServer->close($request->fd);
            return;
        }
        // 解析令牌
        $appToken                   = EnvData::getByHeaderRouteItem(self::HEADER_X_APP_TOKEN, $RouteConf);
        $isNotLoginClose            = false;
        if(is_string($appToken) && strlen($appToken) > 32){
            $devId                  = strval(EnvData::getByHeaderRouteItem(self::HEADER_X_DEV_ID, $RouteConf));
            $appPlatform            = strval(EnvData::getByHeaderRouteItem(self::HEADER_X_APP_PLATFORM, $RouteConf));
            if(!AuthData::init($appToken, $devId, $appPlatform) && $RouteConf->isWebSocketAuth()) $isNotLoginClose = true;
        }else{
            if($RouteConf->isWebSocketAuth()){
                $isNotLoginClose = true;
            }else{
                AuthData::reset();
            }
        }
        if($isNotLoginClose){
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_AUTH_NOT_LOGIN, Lang::get(LangKeyEnum::LANG_SYS_AUTH_NOT_LOGIN));
            ResponseData::pushEnd($WebSocketServer, $request->fd, $RouteConf);
            $WebSocketServer->close($request->fd);
            return;
        }
        // 解析语言
        $devLang                    = EnvData::getByHeaderRouteItem(self::HEADER_X_DEV_LANG, $RouteConf);
        if(!is_string($devLang)){
            $langData               = Http::getAcceptLanguageList();
            if(count($langData) > 0) $devLang = $langData[0][0]??'';
        }
        if(is_string($devLang) && strlen($devLang) > 1) Lang::setLangType($devLang, true);
        // 开始处理请求
        self::start();
        ResponseData::initRspContentHandleType($RouteConf->getRspContentType(), $RouteConf->getRspContentSecret(), $RouteConf->isRspContentSig(), $RouteConf->getAppSecret(), $RouteConf->getSmartyException(), $RouteConf->getSmartyError(), $RouteConf->getSmartyNotFound(), $RouteConf->getSmartyNoPermission(), $RouteConf->getSmartyParamFail());
        // 请求头检查
        RequestHeader::init($RouteConf);    // 初始化
        $errCode                = 0;
        $errMsg                 = '';
        if($RouteConf->isReqHeaderCheck() && !self::checkHeaderData($errCode, $errMsg, $RouteConf->getAllowDiffTime(), $RouteConf)){
            ResponseData::rspErrAuto($errCode, $errMsg);
            ResponseData::pushEnd($WebSocketServer, $request->fd, $RouteConf);
            $WebSocketServer->close($request->fd);
            return;
        }
        $reqContentSrc          = EnvData::getDataByRawContent();
        $reqContentSrcLength    = strlen($reqContentSrc);
        // 验证签名
        if($RouteConf->isReqContentSig() && (!$RouteConf->isSigSkip() || strlen($RouteConf->getSigSkipSecret()) < 1 || $RouteConf->getSigSkipSecret() !== EnvData::getByHeaderRouteItem(self::HEADER_X_SIG_SKIP, $RouteConf))){
            $sigCheckResult             = false;
            $headSrcSig                 = EnvData::getByHeaderRouteItem(self::HEADER_X_SIG, $RouteConf);
            if(is_string($headSrcSig)){
                $headSrcResult          = true;
                $headSrcData            = [];
                foreach (self::$accessHeaderList as $accessHeaderName){
                    if(in_array($accessHeaderName, [self::HEADER_X_SIG, self::HEADER_X_KEY, self::HEADER_X_SIG_SKIP, self::HEADER_X_DEV_OFFSET, self::HEADER_CONTENT_TYPE], true)) continue;
                    $headSrcVal         = EnvData::getByHeaderRouteItem($accessHeaderName, $RouteConf);
                    if($headSrcVal === null){
                        $headSrcResult  = false;
                        break;
                    }
                    $headSrcData[strtolower($accessHeaderName)] = $headSrcVal;
                }
                if($headSrcResult && count($headSrcData) > 0){
                    ksort($headSrcData);
                    $sigSrcStr                      = '';
                    foreach($headSrcData as $headSrcKey => $headSrcVal) $sigSrcStr .= (strlen($sigSrcStr) < 1 ? '' : '&').$headSrcKey.'='.$headSrcVal;
                    // 请求的加密状态
                    $reqContentSecret               = true;
                    switch (strtoupper($RouteConf->getReqContentSecret())){
                        case ResponseData::REQ_CONTENT_SECRET_NONE:{ $reqContentSecret = false; break; }
                        case ResponseData::REQ_CONTENT_SECRET_RAND_AUTO:{
                            $headerKey              = EnvData::getByHeaderRouteItem(ResponseMain::HEADER_X_KEY, $RouteConf);
                            $reqContentSecret       = !(!is_string($headerKey) || strlen($headerKey) < 1 || $headerKey === ResponseData::REQ_CONTENT_SECRET_NONCE);
                            break;
                        }
                    }
                    $sigSrcStr          .= $RouteConf->getAppSecret().($reqContentSrcLength < 1 ? '' : (!$reqContentSecret ? $reqContentSrc : sha1(base64_encode($reqContentSrc))));
                    if($headSrcSig === sha1($sigSrcStr)) $sigCheckResult = true;
                }
            }
            if(!$sigCheckResult){
                ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_HEADER_SIG, Lang::get(LangKeyEnum::LANG_SYS_HEADER_SIG));
                ResponseData::pushEnd($WebSocketServer, $request->fd, $RouteConf);
                $WebSocketServer->close($request->fd);
                return;
            }
        }
        if(!$RouteConf->isWebSocket() || ($RouteConf->isWebSocketAuth() && !AuthData::isLogin())){
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_AUTH_NOT_LOGIN, Lang::get(LangKeyEnum::LANG_SYS_AUTH_NOT_LOGIN));
            ResponseData::pushEnd($WebSocketServer, $request->fd, $RouteConf);
            $WebSocketServer->close($request->fd);
            return;
        }
        // 超过最大同时连接数量时
        $fd                 = $request->fd;
        $time               = CommTime::getTimeStamp();
        $userId             = strval(AuthData::getLoginUserId());
        $envStr             = EnvData::toSaveSocksStr();
        $groupName          = $RouteConf->getGroupName();
        $loginPlatform      = AuthData::getLoginAppPlatform();
        $loginDevId         = AuthData::getLoginDevId();
        $loginKey           = AuthData::getLoginTokenKey();
        $loginExpTime       = AuthData::getLoginTokenKeyExpireTime();
        $loginAuth          = AuthData::getLoginAuthStr();
        $langType           = Lang::getLangType();
        if(!WebSocketTable::saveWsConnTableData($fd, $time, $userId, $envStr, $groupName, $loginPlatform, $loginDevId, $loginKey, $loginExpTime, $loginAuth, $langType)){
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_LIMIT_ERROR, Lang::get(LangKeyEnum::LANG_SYS_LIMIT_ERROR));
            ResponseData::pushEnd($WebSocketServer, $request->fd, $RouteConf);
            $WebSocketServer->close($request->fd);
            echo CommTime::getString().':FAIL: '.$request->fd.', '.WebSocketTable::getWsConnTableRowNum()."\n";
            return;
        }
        ResponseData::wsOpen($fd, $time, $userId, $envStr, $groupName, $loginKey, $loginExpTime, $loginPlatform, $loginDevId, $loginAuth, $langType);
//        echo CommTime::getString().':SUCC: '.$request->fd.', '.WebSocketTable::getWsConnTableRowNum()."\n";
    }

    /**
     * @param Server $WebSocketServer
     * @param Frame $frame
     * @return void
     * serviceMessage
     */
    public static function serviceMessage(Server $WebSocketServer, Frame $frame):void
    {
        switch($frame->opcode){
            case SWOOLE_WEBSOCKET_OPCODE_CONTINUATION:{ // 0x00-附加数据帧
                return;
            }
            case SWOOLE_WEBSOCKET_OPCODE_TEXT:{         // 0x01-TEXT[UTF-8]
                break;
            }
            case SWOOLE_WEBSOCKET_OPCODE_BINARY:{       // 0x02-BINARY
                break;
            }
            case SWOOLE_WEBSOCKET_OPCODE_CLOSE:{        // 0x08-CLOSE 需启用 open_websocket_close_frame
                $WebSocketServer->close($frame->fd);
                return;
            }
            case SWOOLE_WEBSOCKET_OPCODE_PING:{         // 0x09-PING 需启用 open_websocket_ping_frame
                $FramePong          = new Frame();
                $FramePong->opcode  = WEBSOCKET_OPCODE_PONG;
                $WebSocketServer->push($frame->fd, $FramePong);
                return;
            }
            case SWOOLE_WEBSOCKET_OPCODE_PONG:{         // 0x0a-PONG 需启用 open_websocket_pong_frame
                return;
            }
            default:{   // other
                return;
            }
        }
        self::start();
        $groupName          = '';
        if(!self::initMessage($frame->fd, $groupName)){             // 重载初始化失败
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SERVER_EXCEPTION, Lang::get(LangKeyEnum::LANG_SYS_SERVER_EXCEPTION));
            ResponseData::pushEnd($WebSocketServer, $frame->fd);
            WebSocketTable::removeWsConnTableData($frame->fd);
            $WebSocketServer->close($frame->fd);
            return;
        }
        $RouteConf          = Conf::getRouteConfGroup()->getByGroupName($groupName);
        if(!($RouteConf instanceof RouteConf)){                     // 错误groupName
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_GROUP_NAME, Lang::get(LangKeyEnum::LANG_SYS_GROUP_NAME));
            ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf);
            WebSocketTable::removeWsConnTableData($frame->fd);
            $WebSocketServer->close($frame->fd);
            return;
        }
        if(!$RouteConf->isWebSocket()){                             // 不是websocket
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_GROUP_TYPE, Lang::get(LangKeyEnum::LANG_SYS_GROUP_TYPE));
            ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf);
            WebSocketTable::removeWsConnTableData($frame->fd);
            $WebSocketServer->close($frame->fd);
            return;
        }
        ResponseData::initRspContentHandleType($RouteConf->getRspContentType(), $RouteConf->getRspContentSecret(), $RouteConf->isRspContentSig(), $RouteConf->getAppSecret(), $RouteConf->getSmartyException(), $RouteConf->getSmartyError(), $RouteConf->getSmartyNotFound(), $RouteConf->getSmartyNoPermission(), $RouteConf->getSmartyParamFail());
        if($RouteConf->isWebSocketAuth() && !AuthData::isLogin()){  // 认证登入已过期
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_AUTH_NOT_LOGIN, Lang::get(LangKeyEnum::LANG_SYS_AUTH_NOT_LOGIN));
            ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf);
            WebSocketTable::removeWsConnTableData($frame->fd);
            $WebSocketServer->close($frame->fd);
            return;
        }
        // 解密内容
        $reqContent         = '';
        if(!ResponseData::decryptReqContent($RouteConf->getReqContentSecret(), $frame->data, $reqContent, $RouteConf)){                 // 解密失败
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SECRET_DECRYPT, Lang::get(LangKeyEnum::LANG_SYS_SECRET_DECRYPT));
            ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf);
            return;
        }
        // 解码内容
        $reqId              = 0;
        $urlPath            = '';
        $params             = null;
        $isCheckType        = true;
        if(!ResponseData::decodeReqContentSocks($RouteConf->getReqContentType(), $reqContent, $reqId, $urlPath, $params, $isCheckType)){  // 解码失败
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_SECRET_DECODE, Lang::get(LangKeyEnum::LANG_SYS_SECRET_DECODE));
            ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf, $urlPath);
            return;
        }
        ResponseData::setReqId($reqId);
        if(strlen($urlPath) < 1){
            ResponseData::rspErrAuto(ErrCodeEnum::ERR_SYS_REQUEST_EMPTY, Lang::get(LangKeyEnum::LANG_SYS_REQUEST_EMPTY));
            ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf, $urlPath);
            return;
        }
        try{
            $runResult      = self::run($urlPath, $groupName, true, $reqContent, $frame->data, $params, $isCheckType);
            unset($runResult);
        }catch(Exception $Exception){
            self::runExceptionHandler($Exception);
        }
        ResponseData::pushEnd($WebSocketServer, $frame->fd, $RouteConf, $urlPath);
    }

    /**
     * @param Server $WebSocketServer
     * @param int $fd
     * @return void
     * serviceClose
     */
    public static function serviceClose(Server $WebSocketServer, int $fd):void
    {
        $time                       = 0;
        $userId                     = '';
        $envStr                     = '';
        $groupName                  = '';
        $loginKey                   = '';
        $loginExpTime               = 0;
        $loginPlatform              = '';
        $loginDevId                 = '';
        $loginAuth                  = '';
        $langType                   = '';
        WebSocketTable::getWsConnTableData($fd, $time, $userId, $envStr, $groupName, $loginKey, $loginExpTime, $loginPlatform, $loginDevId, $loginAuth, $langType);
        if(self::$isWebSocket || intval($WebSocketServer->getClientInfo($fd)['websocket_status']??0) > 0){  // webSocket断开连接
            WebSocketTable::removeWsConnTableData($fd);
            ResponseData::wsClose($fd, $time, $userId, $envStr, $groupName, $loginKey, $loginExpTime, $loginPlatform, $loginDevId, $loginAuth, $langType);
        }else{  // 非webSocket断开连接(HTTP)
            WebSocketTable::removeWsConnTableData($fd);
        }
    }

    /**
     * @param Server $WebSocketServer
     * @return void
     * serviceInit
     */
    public static function serviceInit(Server $WebSocketServer):void
    {
        !defined('WEBSOCKET_OPCODE_TEXT') && define('WEBSOCKET_OPCODE_TEXT', 1);
        !defined('WEBSOCKET_OPCODE_BINARY') && define('WEBSOCKET_OPCODE_BINARY', 2);
        self::$WebSocketServer          = $WebSocketServer;
        self::$allowMethodList          = [];
        $allowMethodList                = explode(',', Conf::getResponseConf()->getAllowMethodList());
        if(count($allowMethodList) > 0) foreach ($allowMethodList as $allowMethod){
            $allowMethod                = strtoupper(trim($allowMethod));
            if(strlen($allowMethod) > 0 && $allowMethod !== 'OPTIONS') self::$allowMethodList[] = $allowMethod;
        }
        WebSocketTable::init();
    }

    /**
     * @param Server $WebSocketServer
     * @return void
     * serviceStart
     */
    public static function serviceStart(Server $WebSocketServer):void
    {
        Boot::start($WebSocketServer);
    }

    /**
     * @param Server $WebSocketServer
     * @param int $workId
     * @return void
     * serviceWorkerStart
     */
    public static function serviceWorkerStart(Server $WebSocketServer, int $workId):void
    {
        Boot::startWorker($WebSocketServer, $workId);
    }

    /**
     * @param Server $WebSocketServer
     * @param int $workId
     * @return void
     * serviceWorkerExit
     */
    public static function serviceWorkerExit(Server $WebSocketServer, int $workId):void
    {
        Boot::destroy($workId);
        unset($WebSocketServer);
    }

    /**
     * @param Server $WebSocketServer
     * @param Task $Task
     * @return void
     * serviceTaskStart
     */
    public static function serviceTaskStart(Server $WebSocketServer, Task $Task):void
    {
        !defined('IS_RUN_TASK') && define('IS_RUN_TASK', true);
        if($Task->data instanceof TaskAbstract) $Task->data->taskRun();
    }

    /**
     * @param Server $WebSocketServer
     * @param int $taskId
     * @param $finishData
     * @return void
     * serviceTaskFinish
     */
    public static function serviceTaskFinish(Server $WebSocketServer, int $taskId, $finishData):void
    {
    }

    /**
     * @param Server $WebSocketServer
     * @return void
     * serviceReloadBefore
     */
    public static function serviceReloadBefore(Server $WebSocketServer):void
    {
    }

    /**
     * @param Server $WebSocketServer
     * @return void
     * serviceReloadAfter
     */
    public static function serviceReloadAfter(Server $WebSocketServer):void
    {
    }

    /**
     * @return Server|null
     * getWebSocketServer
     */
    public static function getWebSocketServer():?Server
    {
        return self::$WebSocketServer;
    }

    /**
     * @return int
     * getWebSocketFd
     */
    public static function getWebSocketFd():int
    {
        return self::$webSocketFd;
    }

    /**
     * @return string
     * getServerName
     */
    public static function getServerName():string
    {
        return self::$serverName;
    }

    /**
     * @return array
     * getAccessHeaderList
     */
    public static function getAccessHeaderList():array
    {
        return self::$accessHeaderList;
    }
    /**
     * @return int
     * getRunTime
     */
    private static function getRunTime():int
    {
        return CommTime::getTimeMicro(-1*self::$startTimeMicro);
    }

    /**
     * @param string $logString
     * @return void
     * addRunLog
     */
    public static function addRunLog(string $logString):void
    {
        self::$runLogList[]     = $logString;
    }

    /**
     * @param string $msgStr
     * @param string $groupName
     * @param bool $isBinary
     * @return string
     * getPushMsg
     */
    public static function getPushMsg(string $msgStr, string $groupName, bool &$isBinary):string
    {
        if(strlen($msgStr) < 1) return '';
        $RouteConf          = Conf::getRouteConfGroup()->getByGroupName($groupName);
        if(!($RouteConf instanceof RouteConf)) return '';   // 错误groupName
        if(!$RouteConf->isWebSocket()) return '';           // 不是websocket
        // 规则同步来自 ResponseData::setRspContentSecret
        $secretType         = $RouteConf->getRspContentSecret();
        switch (strtoupper($secretType)){
            case ResponseData::RSP_CONTENT_SECRET_NONE:{
                $isSecret       = false;
                $secretKey      = '';
                break;
            }
            case ResponseData::RSP_CONTENT_SECRET_RAND:{
                $isSecret       = true;
                $secretKey      = CommString::genRandStr(16);   // 兼容APP固定长度16
                break;
            }
            case ResponseData::RSP_CONTENT_SECRET_RAND_AUTO:{
                $headerKey      = EnvData::getDataByHeaderItem(ResponseMain::HEADER_X_KEY);
                if(!is_string($headerKey) || strlen($headerKey) < 1 || $headerKey === ResponseData::REQ_CONTENT_SECRET_NONCE){
                    $isSecret   = false;
                    $secretKey  = '';
                }else{
                    $isSecret   = true;
                    $secretKey  = CommString::genRandStr(16);   // 兼容APP固定长度16
                }
                break;
            }
            default:{
                if(strlen($secretType) >= 8){
                    $isSecret   = true;
                    $secretKey  = $secretType;
                }else{
                    $isSecret   = false;
                    $secretKey  = '';
                }
            }
        }
        if($isSecret || strlen($secretKey) > 0){
            $isBinary   = true;
            return Des::encrypt($msgStr, $secretKey, '');
        }
        return $msgStr;
    }

    /**
     * @param string $binaryStr
     * @param int $fd
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @param bool $isBinary
     * @param string $binaryStrResult
     * @return void
     * pushBinary
     */
    public static function pushBinary(string $binaryStr, int $fd = 0, int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0, bool &$isBinary = false, string &$binaryStrResult = ''):void
    {
        $isBinary           = true;
        $fd                 = $fd < 1 ? self::$webSocketFd : $fd;
        if(!self::$WebSocketServer->isEstablished($fd)) return;
        $binaryStrResult    = self::getPushMsg($binaryStr, WebSocketTable::getWsConnTableGroup($fd), $isBinary);
        self::push($fd, $binaryStrResult, $isBinary, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param string $textStr
     * @param int $fd
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @param bool $isBinary
     * @param string $textStrResult
     * @return void
     * pushText
     */
    public static function pushText(string $textStr, int $fd = 0, int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0, bool &$isBinary = false, string &$textStrResult = ''):void
    {
        $isBinary           = false;
        $fd                 = $fd < 1 ? self::$webSocketFd : $fd;
        if(!self::$WebSocketServer->isEstablished($fd)) return;
        $textStrResult      = self::getPushMsg($textStr, WebSocketTable::getWsConnTableGroup($fd), $isBinary);
        self::push($fd, $textStrResult, $isBinary, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param int $fd
     * @param string $data
     * @param bool $isBinary
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * push
     */
    public static function push(int $fd, string $data, bool $isBinary, int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        self::$WebSocketServer->push($fd, $data, $isBinary ? WEBSOCKET_OPCODE_BINARY : WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN);
        if($ackNum > 0 && self::$WebSocketServer->taskworker){  // Task进程
            $currTime       = CommTime::getTimeStamp();
            $expireTime     = $recvWaitTimeOut < 1 ? 0 : ($currTime + ($recvWaitTimeOut+1) * $recvReTryNum);
            if(WebSocketTable::setAckNum($ackNum, $expireTime)){
                self::$ackDataList[$ackNum] = [$fd, $recvReTryNum, $recvWaitTimeOut < 1 ? 0 : $currTime + $recvWaitTimeOut, $isBinary, $data];
            }
        }
    }

    /**
     * @return void
     * handleAckPush
     */
    public static function handleAckPush():void
    {
        $ackDataListSize    = count(self::$ackDataList);
        if($ackDataListSize < 1) return;
        $skipNum            = max($ackDataListSize - 10000, 0); // 最大个数
        foreach (self::$ackDataList as $ackNum => &$ackData){
            if(--$skipNum >= 0){
                WebSocketTable::delAckNum($ackNum);
                unset(self::$ackDataList[$ackNum]);
                continue;
            }
            $fd         = $ackData[0]??-1;
            $tryNum     = $ackData[1]??-1;
            $timeOut    = $ackData[2]??-1;
            if($fd < 1 || $tryNum < 1 || $timeOut < 0 || !self::$WebSocketServer->isEstablished($fd)){
                WebSocketTable::delAckNum($ackNum);
                unset(self::$ackDataList[$ackNum]);
                continue;
            }
            $expireTime     = 0;
            if(!WebSocketTable::getAckNumExpireTime($ackNum, $expireTime)){
                WebSocketTable::delAckNum($ackNum);
                unset(self::$ackDataList[$ackNum]);
                continue;
            }
            $currTime       = CommTime::getTimeStamp();
            if($expireTime > 0 && $expireTime < $currTime){
                WebSocketTable::delAckNum($ackNum);
                unset(self::$ackDataList[$ackNum]);
                continue;
            }
            if($timeOut > CommTime::getTimeStamp()) continue;   // 未达到重发时间
            $isBinary   = $ackData[3]??-1;
            $data       = $ackData[4]??-1;
            if($tryNum === 1){
                unset(self::$ackDataList[$ackNum]);
            }else{
                --$ackData[1];
            }
            self::$WebSocketServer->push($fd, $data, $isBinary ? WEBSOCKET_OPCODE_BINARY : WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN);
        }
    }

    /**
     * @param string $binaryStr
     * @param array $fdList
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryMulti
     */
    public static function pushBinaryMulti(string $binaryStr, array $fdList, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(strlen($binaryStr) < 1 || count($fdList) < 1) return;
        if(count($fdNotList) > 0) $fdList = array_diff($fdList, $fdNotList);
        if(count($fdList) < 1) return;
        $isBinary           = true;
        $binaryStrResult    = '';
        foreach ($fdList as $fd) if($fd > 0){
            if(strlen($binaryStrResult) > 0){
                self::push($fd, $binaryStrResult, $isBinary, $recvReTryNum, $recvWaitTimeOut, $ackNum);
            }else{
                self::pushBinary($binaryStr, $fd, $recvReTryNum, $recvWaitTimeOut, $ackNum, $isBinary, $binaryStrResult);
            }
        }
    }

    /**
     * @param string $textStr
     * @param array $fdList
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextMulti
     */
    public static function pushTextMulti(string $textStr, array $fdList, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(strlen($textStr) < 1 || count($fdList) < 1) return;
        if(count($fdNotList) > 0) $fdList = array_diff($fdList, $fdNotList);
        if(count($fdList) < 1) return;
        $isBinary           = false;
        $textStrResult      = '';
        foreach ($fdList as $fd) if($fd > 0){
            if(strlen($textStrResult) > 0){
                self::push($fd, $textStrResult, $isBinary, $recvReTryNum, $recvWaitTimeOut, $ackNum);
            }else{
                self::pushText($textStr, $fd, $recvReTryNum, $recvWaitTimeOut, $ackNum, $isBinary, $textStrResult);
            }
        }
    }

    /**
     * @param int $userId
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByUserId
     */
    public static function pushBinaryByUserId(int $userId, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if($userId < 1 || strlen($binaryStr) < 1) return;
        self::pushBinaryByUserIdMulti([$userId], $binaryStr, $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $userIdList
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByUserIdMulti
     */
    public static function pushBinaryByUserIdMulti(array $userIdList, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        $userIdListLength               = count($userIdList);
        if($userIdListLength < 1 || strlen($binaryStr) < 1) return;
        if($userIdListLength === 1){
            $fd                         = WebSocketTable::getWsConnTableFdId(strval($userIdList[0]??''));
            if($fd > 0) self::pushBinaryMulti($binaryStr, [$fd], $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
        }else{
            $userIdStrList              = [];
            foreach ($userIdList as $userIdStr){
                if(is_int($userIdStr)){
                    if($userIdStr > 0) $userIdStrList[] = strval($userIdStr);
                    continue;
                }
                if(is_string($userIdStr) && strlen($userIdStr) > 0) $userIdStrList[] = $userIdStr;
            }
            if(count($userIdStrList) < 1) return;
            $userIdStrList              = array_unique($userIdStrList);
            self::pushBinaryMulti($binaryStr, WebSocketTable::getWsConnTableFdIdList($userIdStrList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
        }
    }

    /**
     * @param int $userId
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByUserId
     */
    public static function pushTextByUserId(int $userId, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if($userId < 1 || strlen($textStr) < 1) return;
        self::pushTextByUserIdMulti([$userId], $textStr, $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $userIdList
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByUserIdMulti
     */
    public static function pushTextByUserIdMulti(array $userIdList, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        $userIdListLength               = count($userIdList);
        if($userIdListLength < 1 || strlen($textStr) < 1) return;
        if($userIdListLength === 1){
            $fd                         = WebSocketTable::getWsConnTableFdId(strval($userIdList[0]??''));
            if($fd > 0) self::pushTextMulti($textStr, [$fd], $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
        }else{
            $userIdStrList              = [];
            foreach ($userIdList as $userIdStr){
                if(is_int($userIdStr)){
                    if($userIdStr > 0) $userIdStrList[] = strval($userIdStr);
                    continue;
                }
                if(is_string($userIdStr) && strlen($userIdStr) > 0) $userIdStrList[] = $userIdStr;
            }
            if(count($userIdStrList) < 1) return;
            $userIdStrList              = array_unique($userIdStrList);
            self::pushTextMulti($textStr, WebSocketTable::getWsConnTableFdIdList($userIdStrList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
        }
    }

    /**
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryAll
     */
    public static function pushBinaryAll(string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getAllFdIdList(), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextAll
     */
    public static function pushTextAll(string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getAllFdIdList(), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param bool $isLogin
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByLogin
     */
    public static function pushBinaryByLogin(bool $isLogin, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getFdIdListByToken($isLogin), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param bool $isLogin
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByLogin
     */
    public static function pushTextByLogin(bool $isLogin, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getFdIdListByToken($isLogin), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $platformList
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByPlatform
     */
    public static function pushBinaryByPlatform(array $platformList, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($platformList) < 1 || strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getFdIdListByPlatform($platformList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $platformList
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByPlatform
     */
    public static function pushTextByPlatform(array $platformList, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($platformList) < 1 || strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getFdIdListByPlatform($platformList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $langList
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByLang
     */
    public static function pushBinaryByLang(array $langList, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($langList) < 1 || strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getFdIdListByLang($langList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $langList
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByLang
     */
    public static function pushTextByLang(array $langList, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($langList) < 1 || strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getFdIdListByLang($langList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $devIdList
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByDevId
     */
    public static function pushBinaryByDevId(array $devIdList, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($devIdList) < 1 || strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getFdIdListByDevId($devIdList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $devIdList
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByDevId
     */
    public static function pushTextByDevId(array $devIdList, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($devIdList) < 1 || strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getFdIdListByDevId($devIdList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $groupList
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryByGroup
     */
    public static function pushBinaryByGroup(array $groupList, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($groupList) < 1 || strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getFdIdListByGroup($groupList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $groupList
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextByGroup
     */
    public static function pushTextByGroup(array $groupList, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($groupList) < 1 || strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getFdIdListByGroup($groupList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $subKeyList
     * @param string $binaryStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushBinaryBySubKey
     */
    public static function pushBinaryBySubKey(array $subKeyList, string $binaryStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($subKeyList) < 1 || strlen($binaryStr) < 1) return;
        self::pushBinaryMulti($binaryStr, WebSocketTable::getFdIdListBySubKey($subKeyList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }

    /**
     * @param array $subKeyList
     * @param string $textStr
     * @param array $fdNotList
     * @param int $recvReTryNum
     * @param int $recvWaitTimeOut
     * @param int $ackNum
     * @return void
     * pushTextBySubKey
     */
    public static function pushTextBySubKey(array $subKeyList, string $textStr, array $fdNotList = [], int $recvReTryNum = 0, int $recvWaitTimeOut = 2, int $ackNum = 0):void
    {
        if(count($subKeyList) < 1 || strlen($textStr) < 1) return;
        self::pushTextMulti($textStr, WebSocketTable::getFdIdListBySubKey($subKeyList), $fdNotList, $recvReTryNum, $recvWaitTimeOut, $ackNum);
    }
}